## main.py

import pygame
from typing import Dict, List
from constants import COLORS, TILE_SIZE, MARGIN, DIRECTIONS, DIFFICULTY_LEVELS
from board import Board
from difficulty import Difficulty
from ui import UI

## Main class to start and run the game
class Main:
    """
    Class to start and manage the main game loop.
    """
    
    def __init__(self):
        """
        Initialize the Main class and set up the game.
        """
        self._game: Game = Game()

    def main(self) -> None:
        """
        Main function to start the game.
        """
        pygame.init()
        self._game.start()
        
        # Game loop
        while True:
            self._game.update()
            if self._game.check_game_over():
                # Display game over and wait for restart
                while True:
                    event = pygame.event.wait()
                    if event.type == pygame.MOUSEBUTTONDOWN:
                        self._game._ui.handle_input(event, self._game)
                    elif event.type == pygame.QUIT:
                        pygame.quit()
                        return

## Game class to manage the core game logic
class Game:
    """
    Class to manage the core game logic for the 2048 game. It handles starting, updating, 
    and resetting the game, as well as managing the game over state and score.
    """
    
    def __init__(self):
        """
        Initialize the Game class with the board, difficulty, UI, and other necessary attributes.
        """
        self._board: Board = None
        self._difficulty: Difficulty = Difficulty()
        self._ui: UI = None
        self._score: int = 0
        self._game_over: bool = False

    def start(self) -> None:
        """
        Start the game by initializing the board, UI, and setting up the Pygame window.
        """
        self._board = Board(self._difficulty.get_board_size())
        self._ui = UI(self._difficulty.get_board_size(), self._difficulty.get_difficulty())
        self._score = 0
        self._game_over = False
        self._ui.render_board(self._board)
        self._ui.display_score(self._score)
        self._ui.display_restart_button()

    def update(self) -> None:
        """
        Update the game state by handling user input and checking if the game is over.
        """
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return
            elif event.type == pygame.MOUSEBUTTONDOWN:
                self._handle_restart_button_click(event)
            self._ui.handle_input(event, self)

        if not self._game_over and self.check_game_over():
            self._game_over = True
            self._ui.display_game_over()

    def check_game_over(self) -> bool:
        """
        Check if the game is over by verifying if there are any available moves left.

        Returns:
        bool: True if the game is over, False otherwise.
        """
        if self._board.get_available_cells():
            return False
        for i in range(self._board.get_board_size()):
            for j in range(self._board.get_board_size() - 1):
                if self._board._tiles[i][j] == self._board._tiles[i][j + 1] or \
                   self._board._tiles[j][i] == self._board._tiles[j + 1][i]:
                    return False
        return True

    def reset_game(self) -> None:
        """
        Reset the game by reinitializing the board and UI, and resetting the score and game over state.
        """
        self._board.init_board()
        self._score = 0
        self._game_over = False
        self._ui.render_board(self._board)
        self._ui.display_score(self._score)
        self._ui.display_restart_button()

    def move_tiles(self, direction: str) -> None:
        """
        Move the tiles in the specified direction and update the UI and score accordingly.

        Args:
        direction (str): The direction to move the tiles. Must be one of the values from constants.DIRECTIONS.
        """
        if direction in DIRECTIONS.values():
            move_successful: bool = self._board.move_tiles(direction)
            if move_successful:
                self._board.add_new_tile()
                self._score = self._board.get_score()
                self._ui.render_board(self._board)
                self._ui.display_score(self._score)

    def _handle_restart_button_click(self, event: pygame.event.Event) -> None:
        """
        Handle the restart button click event.

        Args:
        event (pygame.event.Event): The event to handle.
        """
        mouse_pos = pygame.mouse.get_pos()
        restart_button_x_min = (self._board.get_board_size() * (TILE_SIZE + MARGIN) - 100 + MARGIN)
        restart_button_x_max = restart_button_x_min + 110
        restart_button_y_min = MARGIN
        restart_button_y_max = restart_button_y_min + 40

        if (restart_button_x_min < mouse_pos[0] < restart_button_x_max and
            restart_button_y_min < mouse_pos[1] < restart_button_y_max):
            self.reset_game()

# Entry point of the application
if __name__ == "__main__":
    main = Main()
    main.main()
